/**
 * 
 */
package qy.qyalgotrader.mystrategy;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections4.MapUtils;
import org.apache.commons.collections4.map.ListOrderedMap;

import com.google.common.collect.ArrayTable;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;

import qy.jalgotrade.bar.Bar;
import qy.jalgotrade.bar.Bars;
import qy.jalgotrade.barfeed.BaseBarFeed;
import qy.jalgotrade.broker.backtesting.BacktestingBroker;
import qy.jalgotrade.broker.backtesting.TradePercentage;
import qy.jalgotrade.broker.fillstrategy.DefaultStrategy;
import qy.jalgotrade.broker.fillstrategy.FillStrategy;
import qy.jalgotrade.strategy.BacktestingStrategy;
import qy.jalgotrade.strategy.position.Position;
import qy.qyalgotrader.mystrategy.StratUtils.RiskMan;

/**
 * @author c-geo
 *
 */
public abstract class MultiFreqsTraderBase extends BacktestingStrategy {

	// @formatter:off
	public static final Map<String, Object> DEFAULT_OPT_PARAMS = MapUtils.putAll(new ListOrderedMap<>(), new Object[][] {
		// 风险管理:
		{"riskMan", MapUtils.putAll(new ListOrderedMap<>(), new Object[][] {
			{"hardSl", 0.005},
			{"cashUtil", 0.55},
			{"volaEstim", 0.03}
		})},
		// 多周期参数:
		{"freqsMan", MapUtils.putAll(new ListOrderedMap<>(), new Object[][] {
			{"MICRO", "MINUTE"},
			{"SHORT", "MINUTE_3"},
			{"MEDIUM", "MINUTE_5"},
			{"LONG", "MINUTE_15"},
			{"TREND_RECOG", "MINUTE_30"}
		})},
		// 信号周期:
		{"signalFreq", null},
		// 回测模拟 Broker 参数:
		{"cashOrBrokerParams", MapUtils.putAll(new ListOrderedMap<>(), new Object[][] {
			{"initialCash", 1000000.0},
			{"commission", 0.0,},
			{"allowNegativeCash", true},
			{"fillStrategy.noVolumeLimit", true}
		})},
	});
	// @formatter:on

	/**
	 * 标的:
	 */
	private List<String> __instruments;

	/**
	 * 风险管理:
	 */
	private RiskMan __riskMan;

	/**
	 * 多时间周期管理:
	 */
	private Map<StratUtils.MultiFrequencyMeasure, Bar.Frequency> __freqsMan;

	/**
	 * 策略的信号时间周期, 即主时间周期:
	 */
	private Bar.Frequency __signalFreq;

	/**
	 * 保存多时间周期 BarFeed, Key: enum(pyalgotrade.bar.Frequency), Value: BarFeed:
	 */
	private Map<Bar.Frequency, BaseBarFeed> __feedMfDict;

	/**
	 * 保存多周期技术指标: {Frequency: techIdnDict {techIdnName: {instrument: techIndcs}}}:
	 */
	private Table<Bar.Frequency, String, Map<String, Object>> __mfTechIndDicts;

	/**
	 * 多头头寸: {instrument: {"longPos": LongPos, "entryFreq": Freq}}
	 */
	private Table<String, String, Object> __longPosDict;

	/**
	 * 空头头寸: {instrument: {"shortPos": ShortPos, "entryFreq": Freq}}
	 */
	private Table<String, String, Object> __shortPosDict;

	/**
	 * 
	 * @param barFeed
	 * @param broker
	 * @throws Exception
	 */
	public MultiFreqsTraderBase(BaseBarFeed barFeed, List<String> instruments) throws Exception {

		this(barFeed, instruments, DEFAULT_OPT_PARAMS);
	}

	/**
	 * 
	 * @param barFeed
	 * @param broker
	 * @param optParams
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	public MultiFreqsTraderBase(BaseBarFeed barFeed, List<String> instruments, Map<String, Object> optParams)
	        throws Exception {

		super();
		// 初始化回测 Broker:
		Map<String, Object> cashOrBrokerParams = (Map<String, Object>) optParams.getOrDefault("cashOrBrokerParams",
		        DEFAULT_OPT_PARAMS.get("cashOrBrokerParams"));
		BacktestingBroker broker = new BacktestingBroker((double) cashOrBrokerParams.get("initialCash"), barFeed,
		        new TradePercentage((double) cashOrBrokerParams.get("commission")));
		broker.setAllowNegativeCash((boolean) cashOrBrokerParams.get("allowNegativeCash"));
		if ((boolean) cashOrBrokerParams.get("fillStrategy.noVolumeLimit")) {
			FillStrategy bfs = broker.getFillStrategy();
			((DefaultStrategy) bfs).setNoVolumeLimit();
		}

		_init(barFeed, broker);

		// 标的:
		__instruments = instruments;
		// 风险管理:
		__riskMan = RiskMan
		        .create((Map<String, Object>) optParams.getOrDefault("riskMan", DEFAULT_OPT_PARAMS.get("riskMan")));
		// 多时间周期管理:
		__freqsMan = new HashMap<>();
		for (Map.Entry<String, String> e : ((Map<String, String>) optParams.getOrDefault("freqsMan",
		        DEFAULT_OPT_PARAMS.get("freqsMan"))).entrySet()) {
			__freqsMan.put(StratUtils.MultiFrequencyMeasure.valueOf(e.getKey()), Bar.Frequency.valueOf(e.getValue()));
		}
		// 策略的信号时间周期, 即主时间周期 (缺省为同 BarFeed 的周期):
		__signalFreq = (Bar.Frequency) optParams.getOrDefault("signalFreq", DEFAULT_OPT_PARAMS.get("signalFreq"));
		if (__signalFreq == null) {
			__signalFreq = getFeed().getFrequency();
		}
		// 保存多时间周期 BarFeed, Key: enum(pyalgotrade.bar.Frequency), Value: BarFeed:
		__feedMfDict = new HashMap<>();
		__feedMfDict.put(getFeed().getFrequency(), getFeed()); // 源 BarFeed
		// 以 self.__feed 为源, 创建 ResampleBarFeed:
		if (getFeed().getFrequency() != __signalFreq) {
			__feedMfDict.put(__signalFreq, resampleBarFeed(__signalFreq, bars -> onSignalBars(bars)));
		}
		for (Bar.Frequency e : __freqsMan.values()) {
			// e not in [getFeed().getFrequency(), __signalFreq]:
			if (!Arrays.asList(getFeed().getFrequency(), __signalFreq).contains(e)) {
				__feedMfDict.put(e, resampleBarFeed(e, bars -> onResampledBars(bars)));
			}
		}
		// 保存多周期技术指标: {Frequency: techIdnDict {techIdnName: {instrument: techIndcs}}}:
		__mfTechIndDicts = HashBasedTable.create();
		// 头寸管理:
		__longPosDict = ArrayTable.create(__instruments, Arrays.asList("longPos", "entryFreq"));
		__shortPosDict = ArrayTable.create(__instruments, Arrays.asList("shortPos", "entryFreq"));
		for (String e : __instruments) {
			__longPosDict.put(e, "longPos", null);
			__longPosDict.put(e, "entryFreq", null);
			__shortPosDict.put(e, "shortPos", null);
			__shortPosDict.put(e, "entryFreq", null);
		}
	}

	/**
	 * 
	 * @return
	 */
	public List<String> getInstruments() {

		return __instruments;
	}

	/**
	 * 
	 * @return
	 */
	public RiskMan getRiskMan() {

		return __riskMan;
	}

	/**
	 * 
	 * @return
	 */
	public Map<StratUtils.MultiFrequencyMeasure, Bar.Frequency> getFreqsMan() {

		return __freqsMan;
	}

	/**
	 * 
	 * @return
	 */
	public Bar.Frequency getSignalFreq() {

		return __signalFreq;
	}

	/**
	 * 保存多时间周期 BarFeed, Key: enum(pyalgotrade.bar.Frequency), Value: BarFeed:
	 * 
	 * @return
	 */
	public Map<Bar.Frequency, BaseBarFeed> getFeedMfDict() {

		return __feedMfDict;
	}

	/**
	 * 保存多周期技术指标: {Frequency: techIdnDict {techIdnName: {instrument: techIndcs}}}:
	 * 
	 * @return
	 */
	public Table<Bar.Frequency, String, Map<String, Object>> getMfTechIndDicts() {

		return __mfTechIndDicts;
	}

	/**
	 * 
	 * @return
	 */
	public Table<String, String, Object> getLongPosDict() {

		return __longPosDict;
	}

	/**
	 * 
	 * @return
	 */
	public Table<String, String, Object> getShortPosDict() {

		return __longPosDict;
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.strategy.BaseStrategy#onEnterCanceled(qy.jalgotrade.strategy.position.Position)
	 */
	@Override
	public void onEnterCanceled(Position position) {

		for (String e : __longPosDict.rowKeySet()) {
			if (position.equals(__longPosDict.get(e, "longPos"))) {
				__longPosDict.put(e, "longPos", null);
				__longPosDict.put(e, "entryFreq", null);
			}
		}

		for (String e : __shortPosDict.rowKeySet()) {
			if (position.equals(__shortPosDict.get(e, "shortPos"))) {
				__shortPosDict.put(e, "shortPos", null);
				__shortPosDict.put(e, "entryFreq", null);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.strategy.BaseStrategy#onExitOk(qy.jalgotrade.strategy.position.Position)
	 */
	@Override
	public void onExitOk(Position position) {

		for (String e : __longPosDict.rowKeySet()) {
			if (position.equals(__longPosDict.get(e, "longPos"))) {
				__longPosDict.put(e, "longPos", null);
				__longPosDict.put(e, "entryFreq", null);
			}
		}

		for (String e : __shortPosDict.rowKeySet()) {
			if (position.equals(__shortPosDict.get(e, "shortPos"))) {
				__shortPosDict.put(e, "shortPos", null);
				__shortPosDict.put(e, "entryFreq", null);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.strategy.BaseStrategy#onBars(qy.jalgotrade.bar.Bars)
	 */
	@Override
	public void onBars(Bars bars) throws Exception {

		onOriginalBarFeedBars(bars);

		/*
		 * 源 BarFeed 周期生成 Bars 后调用 (若 源 BarFeed 周期 与 __signalFreq 相同, 则不会为 __signalFreq 生成 resampled BarFeed):
		 */
		if (getFeed().getFrequency() == __signalFreq) {
			onSignalBars(bars);
		}
	}

	/**
	 * 源 BarFeed 生成 Bars 时调用 (小级别周期的风控, ...):
	 * @param bars
	 */
	protected void onOriginalBarFeedBars(Bars bars) {

	}

	/**
	 * {@link #getSignalFreq()} 与 {@link #getFeed()} 的 frequency 相同时, 才会调用:
	 * 
	 * @param bars
	 */
	protected abstract void onSignalBars(Bars bars);

	/**
	 * 
	 * @param bars
	 */
	protected abstract void onResampledBars(Bars bars);
}
